<!DOCTYPE html>
<html lang="en-US">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title>在线文档的网络层设计思考 | 被删的前端游乐场</title>
    <meta name="generator" content="VuePress 1.8.2">
    
    <meta name="description" content="Just playing around">
    
    <link rel="preload" href="/front-end-playground/assets/css/0.styles.6ad2a9ca.css" as="style"><link rel="preload" href="/front-end-playground/assets/js/app.1e2670bf.js" as="script"><link rel="preload" href="/front-end-playground/assets/js/2.38d016d1.js" as="script"><link rel="preload" href="/front-end-playground/assets/js/3.e3f029cb.js" as="script"><link rel="preload" href="/front-end-playground/assets/js/34.72087669.js" as="script">
    <link rel="stylesheet" href="/front-end-playground/assets/css/0.styles.6ad2a9ca.css">
  </head>
  <body>
    <div id="app" data-server-rendered="true"><div class="theme-container"><header class="navbar"><div class="sidebar-button"><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" role="img" viewBox="0 0 448 512" class="icon"><path fill="currentColor" d="M436 124H12c-6.627 0-12-5.373-12-12V80c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12z"></path></svg></div> <a href="/front-end-playground/" class="home-link router-link-active"><!----> <span class="site-name">被删的前端游乐场</span></a> <div class="links"><div class="search-box"><input aria-label="Search" autocomplete="off" spellcheck="false" value=""> <!----></div> <nav class="nav-links can-hide"><div class="nav-item"><a href="/front-end-playground/" class="nav-link">概述</a></div><div class="nav-item"><a href="/front-end-playground/front-end-basic/" class="nav-link router-link-active">前端领域</a></div><div class="nav-item"><a href="/front-end-playground/vue/" class="nav-link">Vue学习</a></div><div class="nav-item"><a href="/front-end-playground/wxapp/" class="nav-link">小程序学习</a></div><div class="nav-item"><a href="/front-end-playground/front-end-others/" class="nav-link">百家齐放</a></div><div class="nav-item"><a href="/front-end-playground/front-end-addon/" class="nav-link">前端的进击</a></div><div class="nav-item"><a href="/front-end-playground/front-end-work/" class="nav-link">前端与工作</a></div><div class="nav-item"><a href="/front-end-playground/faq.html" class="nav-link">FAQ</a></div> <a href="https://github.com/godbasin/front-end-playground" target="_blank" rel="noopener noreferrer" class="repo-link">
    Github
    <span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></nav></div></header> <div class="sidebar-mask"></div> <aside class="sidebar"><nav class="nav-links"><div class="nav-item"><a href="/front-end-playground/" class="nav-link">概述</a></div><div class="nav-item"><a href="/front-end-playground/front-end-basic/" class="nav-link router-link-active">前端领域</a></div><div class="nav-item"><a href="/front-end-playground/vue/" class="nav-link">Vue学习</a></div><div class="nav-item"><a href="/front-end-playground/wxapp/" class="nav-link">小程序学习</a></div><div class="nav-item"><a href="/front-end-playground/front-end-others/" class="nav-link">百家齐放</a></div><div class="nav-item"><a href="/front-end-playground/front-end-addon/" class="nav-link">前端的进击</a></div><div class="nav-item"><a href="/front-end-playground/front-end-work/" class="nav-link">前端与工作</a></div><div class="nav-item"><a href="/front-end-playground/faq.html" class="nav-link">FAQ</a></div> <a href="https://github.com/godbasin/front-end-playground" target="_blank" rel="noopener noreferrer" class="repo-link">
    Github
    <span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></nav>  <ul class="sidebar-links"><li><section class="sidebar-group collapsable depth-0" style="padding-top:10px;"><div class="kitty-main" data-v-2b653b36><span class="stand" data-v-2b653b36></span> <div class="cat" data-v-2b653b36><div class="body" data-v-2b653b36></div> <div class="head" data-v-2b653b36><div class="ear" data-v-2b653b36></div> <div class="ear" data-v-2b653b36></div></div> <div class="face" data-v-2b653b36><div class="nose" data-v-2b653b36></div> <div class="whisker-container" data-v-2b653b36><div class="whisker" data-v-2b653b36></div> <div class="whisker" data-v-2b653b36></div></div> <div class="whisker-container" data-v-2b653b36><div class="whisker" data-v-2b653b36></div> <div class="whisker" data-v-2b653b36></div></div></div> <div class="tail-container" data-v-2b653b36><div class="tail" data-v-2b653b36><div class="tail" data-v-2b653b36><div class="tail" data-v-2b653b36><div class="tail" data-v-2b653b36><div class="tail" data-v-2b653b36><div class="tail" data-v-2b653b36><div class="tail" data-v-2b653b36></div></div></div></div></div></div></div></div></div></div> <p class="sidebar-heading open"><span>前端架构</span> <span class="arrow down"></span></p> <ul class="sidebar-links sidebar-group-items"><li><a href="/front-end-playground/front-end-basic/deep-learning/module-seperate.html" class="sidebar-link">谈谈依赖和解耦</a></li><li><a href="/front-end-playground/front-end-basic/deep-learning/trace-stash.html" class="sidebar-link">大型前端项目要怎么跟踪和分析函数调用链</a></li><li><a href="/front-end-playground/front-end-basic/deep-learning/build-application.html" class="sidebar-link">前端构建大型应用</a></li><li><a href="/front-end-playground/front-end-basic/deep-learning/reactive-programing.html" class="sidebar-link">响应式编程在前端领域的应用</a></li><li><a href="/front-end-playground/front-end-basic/deep-learning/vscode-event.html" class="sidebar-link">VSCode 源码解读：事件系统设计</a></li><li><a href="/front-end-playground/front-end-basic/deep-learning/vscode-ipc.html" class="sidebar-link">VSCode 源码解读：IPC通信机制</a></li><li><a href="/front-end-playground/front-end-basic/deep-learning/online-doc-network.html" aria-current="page" class="active sidebar-link">在线文档的网络层设计思考</a><ul class="sidebar-sub-headers"><li class="sidebar-sub-header"><a href="/front-end-playground/front-end-basic/deep-learning/online-doc-network.html#网络层职责" class="sidebar-link">网络层职责</a></li><li class="sidebar-sub-header"><a href="/front-end-playground/front-end-basic/deep-learning/online-doc-network.html#网络层设计" class="sidebar-link">网络层设计</a></li><li class="sidebar-sub-header"><a href="/front-end-playground/front-end-basic/deep-learning/online-doc-network.html#接入层" class="sidebar-link">接入层</a></li></ul></li><li><a href="/front-end-playground/front-end-basic/deep-learning/front-end-performance-analyze.html" class="sidebar-link">补齐Web前端性能分析的工具盲点</a></li><li><a href="/front-end-playground/front-end-basic/deep-learning/monitor-and-report.html" class="sidebar-link">前端监控体系搭建</a></li><li><a href="/front-end-playground/front-end-basic/deep-learning/why-spreadsheet-app-excited.html" class="sidebar-link">在线Excel项目到底有多刺激</a></li><li><a href="/front-end-playground/front-end-basic/deep-learning/task-runner-design.html" class="sidebar-link">如何设计一个任务管理器</a></li><li><a href="/front-end-playground/front-end-basic/deep-learning/network-design-responsibility-driven-design.html" class="sidebar-link">在线文档的网络层开发思考--职责驱动设计</a></li><li><a href="/front-end-playground/front-end-basic/deep-learning/network-design-dependency-decoupling.html" class="sidebar-link">在线文档的网络层开发思考--依赖关系梳理</a></li></ul></section></li><li><section class="sidebar-group collapsable depth-0" style="padding-top:;"><!----> <p class="sidebar-heading"><span>前端深入理解</span> <span class="arrow right"></span></p> <!----></section></li><li><section class="sidebar-group collapsable depth-0" style="padding-top:;"><!----> <p class="sidebar-heading"><span>前端入门</span> <span class="arrow right"></span></p> <!----></section></li></ul> </aside> <main class="page"> <div class="theme-default-content content__default"><p>像在线文档这样大型的项目，不管是从功能职责方面，还是从代码维护方面，分层和分模块都是必然的趋势。而网络层作为与服务端直接连接的一层，有多少是我们可以做到更好的呢？</p> <h1 id="认识网络层"><a href="#认识网络层" class="header-anchor">#</a> 认识网络层</h1> <p>首先，涉及多人在线协作的场景，从用户交互到服务端存储都会特别复杂。对于前端来说，从后台获取的数据到展示，分别需要经过网络层、数据层和渲染层。除此之外，多人在线同样涉及房间管理等，简单来说，我们大致可以这么进行分层（图1）：</p> <p><img src="https://github-imglib-1255459943.cos.ap-chengdu.myqcloud.com/tencent-doc-network_0.png" alt="图1"></p> <h2 id="网络层职责"><a href="#网络层职责" class="header-anchor">#</a> 网络层职责</h2> <p>一般来说，网络层无非就是做一些与服务端通信的工作，例如发起请求、异常处理、自动重试、登录态续期等。如果说，除了 HTTP 请求，可能还涉及 socket、长连接、请求数据缓存等各种功能。</p> <p>在多人协作的场景下，为了保证用户体验，一般会采用 OT 算法来进行冲突处理。而为了保证每次的用户操作都可以按照正确的时序来更新，我们会维护一个自增的版本号，每次有新的修改，都会更新版本号。因此，在这样的场景下，网络层的职责大概包括：</p> <ul><li>校验数据合法性</li> <li>本地数据准确的提交给后台（涉及队列和版本控制）</li> <li>协同数据正确处理后分发给数据层（涉及冲突处理）</li></ul> <p>我们能看到，与网络层有交接的主要包括服务端和数据层。</p> <p>那么，我们可以考虑将网络层拆分模块：
<strong>1. 连接层：管理与服务端的连接（Socket、长连接等）。</strong> <strong>2. 接入层：管理数据版本、冲突处理、与数据层的连接等。</strong></p> <p>这样，我们的分层结构调整为图2：</p> <p><img src="https://github-imglib-1255459943.cos.ap-chengdu.myqcloud.com/tencent-doc-network_1.jpg" alt="图2"></p> <h2 id="网络层设计"><a href="#网络层设计" class="header-anchor">#</a> 网络层设计</h2> <p>既然对网络层进行了模块的拆分，那么相关的设计我们也来分模块进行吧。</p> <h3 id="连接层"><a href="#连接层" class="header-anchor">#</a> 连接层</h3> <p>连接层作为直接与服务端连接的一层，需要包括以下能力（图3）：</p> <ul><li><strong>多种通信方式支持</strong>（长连接、socket、短连接、SSE等）</li> <li><strong>房间管理</strong>（心跳管理、用户管理、消息管理）</li></ul> <p><img src="https://github-imglib-1255459943.cos.ap-chengdu.myqcloud.com/tencent-doc-network_2.png" alt="图3"></p> <p><strong>1. 多种通信方式支持。</strong></p> <p>前后端通信方式有很多种，常见的包括 HTTP 短轮询（pollinf）、Websocket、HTTP 长轮询（long-polling）、SSE（Server-Sent Events）等。</p> <p>我们也能看到，不同的在线文档团队选用的通信方式并不一致。例如谷歌文档上行数据使用 Ajax、下行数据使用 HTTP 长轮询推送；石墨文档上行数据使用 Ajax、下行数据使用 SSE 推送；金山文档、飞书文档、腾讯文档则都使用了 Websocket 传输。</p> <p>而每种通信方式都有各自的优缺点，包括兼容性、资源消耗、实时性等，也有可能跟业务团队自身的后台架构有关系。因此我们在设计连接层的时候，考虑接口拓展性，应该预留对各种方式的支持。</p> <p><strong>2. 房间管理。</strong></p> <p>由于多人协同的需要，相比普通的 Web 页面，还多了房间和用户的管理。在同一个文档中的用户，可视作在同一个房间。除了能看到哪些人在同一个房间以外，我们能收到相互之间的消息，在文档的场景中，用户的每一个操作，都可以作为是一个消息。</p> <p>但文档和一般的房间聊天不一样的地方在于，用户的操作内容可能会很大，例如用户复制粘贴了一个10W、20W的表格内容，这样的消息显然无法一次性传输完。在这种情况下，除了考虑像 Websocket 这种需要自行进行数据压缩（HTTP 本身支持压缩）以外，我们还需要实现自己的分片逻辑。当涉及数据分片之后，紧接而来的还有如何分片、分片数据丢失的一些情况处理。</p> <p>这样，我们的连接层则演化成图4的架构：</p> <p><img src="https://github-imglib-1255459943.cos.ap-chengdu.myqcloud.com/tencent-doc-network_3.jpg" alt="图4"></p> <h2 id="接入层"><a href="#接入层" class="header-anchor">#</a> 接入层</h2> <p>接入层主要负责与业务比较相关的一些内容，例如协同数据的版本管理、冲突处理、用户操作的任务队列。我们可以从接收和发送两个方向来进行拆分（图5）：</p> <ul><li><strong>接收数据</strong>（服务端 -&gt; 数据层）：管理来自服务端的数据</li> <li><strong>发送数据</strong>（数据层 -&gt; 服务端）：管理需要提交给服务端的数据</li></ul> <p><img src="https://github-imglib-1255459943.cos.ap-chengdu.myqcloud.com/tencent-doc-network_4.png" alt="图5"></p> <p>接入层的职责主要包括两个部分：
<strong>1. 维护数据任务队列。</strong></p> <ul><li>用户操作数据正常进入队列</li> <li>队列任务正常提交到接入层</li> <li>队列任务提交异常后进行重试</li> <li>队列任务确认提交成功后移除</li></ul> <p><strong>2. 数据版本有序递增。</strong></p> <ul><li>协同数据版本更新</li> <li>丢失数据版本补拉</li> <li>提交数据版本递增</li></ul> <p>也就是说，来自数据层的数据，将会添加到任务队列中。任务队列中的数据在提交数据之前，需要检查本地的版本和服务端的版本是否一致，如果有版本缺失的话，则要先从服务端拉取缺失的版本，确认本地版本最新后，则可以提交到服务端。</p> <p>而来自服务端的数据，则需要先和任务队列中的数据进行冲突处理。冲突处理完成之后，则会同步到数据层。</p> <p>这里面其实还有更多的细节，包括队列中任务的状态、任务的合并、数据版本的合并、版本断层的处理、重试失败的处理，队列中任务与协同消息的冲突处理、撤销重做的反向冲突处理，甚至还可能涉及断网离线的操作、本地缓存的任务队列、离线数据与在线数据的同步等等。本文我们就不继续讨论这些细节，还是回归到整体的设计上。</p> <p>到这，我们的网络层架构大概出来了：</p> <p><img src="https://github-imglib-1255459943.cos.ap-chengdu.myqcloud.com/tencent-doc-network_5.png" alt="图6"></p> <h1 id="结束语"><a href="#结束语" class="header-anchor">#</a> 结束语</h1> <p>相比其他常见前端项目，在线文档光是网络层的设计都要复杂很多。而网络层只是其中一小部分，我们还有数据层、渲染层、各个组件和 feature 模块，依赖管理、通信管理、流程控制、性能优化等各种功能模块，每一个都有特别多的挑战。对于前端来说，能参与这样一个项目也是一件很幸福的事情了。</p></div> <!----> <footer class="page-edit"><div class="edit-link"><a href="https://github.com/godbasin/front-end-playground/edit/sourcecode/docs/front-end-basic/deep-learning/online-doc-network.md" target="_blank" rel="noopener noreferrer">帮阿猪改善此页面！</a> <span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></div> <!----> <blockquote>部分文章中使用了一些网站的截图，如果涉及侵权，请告诉我删一下谢谢~</blockquote> <div style="margin-top:30px;"><div class="el-row" style="margin-left:-10px;margin-right:-10px;"><div class="el-col el-col-24 el-col-sm-0 el-col-md-2 el-col-lg-4" style="padding-left:10px;padding-right:10px;display:block;"><div style="width:1px;height:1px;"></div></div> <div class="el-col el-col-24 el-col-sm-24 el-col-md-18 el-col-lg-16" style="padding-left:10px;padding-right:10px;"><div class="el-card box-card is-always-shadow"><div class="el-card__header"><div class="clearfix"><span>温馨提示喵</span></div></div><div class="el-card__body"> <div class="el-row" style="margin-left:-10px;margin-right:-10px;"><div class="el-col el-col-24 el-col-xs-24 el-col-sm-12" style="padding-left:10px;padding-right:10px;"><div class="el-image"><div class="image-slot"><img src="https://github-imglib-1255459943.cos.ap-chengdu.myqcloud.com/assets/img/loading.gif" style="width:100%;"></div><!----></div></div> <div class="el-col el-col-24 el-col-xs-24 el-col-sm-12" style="padding-left:10px;padding-right:10px;"><div class="copyright-text"><div>本文版权归作者所有，欢迎转载，但未经作者同意必须保留此段声明，且在文章页面明显位置给出原文连接，否则保留追究法律责任的权利。</div> <div>出处：被删的前端游乐场</div> <div>作者：<a href="https://github.com/godbasin" target="_blank">被删</a></div></div></div></div></div></div></div></div></div></footer> <div class="page-nav"><p class="inner"><span class="prev">
        ←
        <a href="/front-end-playground/front-end-basic/deep-learning/vscode-ipc.html" class="prev">
          VSCode 源码解读：IPC通信机制
        </a></span> <span class="next"><a href="/front-end-playground/front-end-basic/deep-learning/front-end-performance-analyze.html">
          补齐Web前端性能分析的工具盲点
        </a>
        →
      </span></p></div>  <div class="gitalk-container theme-default-content"><div id="gitalk-container" class="content"></div></div></main> <div id="kitty-container"><span><div role="tooltip" id="el-popover-4987" aria-hidden="true" class="el-popover el-popper" style="width:undefinedpx;display:none;"><!----><img src="https://github-imglib-1255459943.cos.ap-chengdu.myqcloud.com/2code2.jpg" class="image"> <div class="text">牡羊猪的猫粮罐</div> </div><span class="el-popover__reference-wrapper"><div id="kitty" style="background:url(https://github-imglib-1255459943.cos.ap-chengdu.myqcloud.com/assets/img/kitty1.svg);"></div></span></span> <div class="el-dialog__wrapper" style="display:none;"><div role="dialog" aria-modal="true" aria-label="牡羊猪是这样渐渐胖成猪的喵（点击图片可以切换噢）" class="el-dialog" style="margin-top:15vh;"><div class="el-dialog__header"><span class="el-dialog__title">牡羊猪是这样渐渐胖成猪的喵（点击图片可以切换噢）</span><button type="button" aria-label="Close" class="el-dialog__headerbtn"><i class="el-dialog__close el-icon el-icon-close"></i></button></div><!----><!----></div></div></div></div><div class="global-ui"></div></div>
    <script src="/front-end-playground/assets/js/app.1e2670bf.js" defer></script><script src="/front-end-playground/assets/js/2.38d016d1.js" defer></script><script src="/front-end-playground/assets/js/3.e3f029cb.js" defer></script><script src="/front-end-playground/assets/js/34.72087669.js" defer></script>
  </body>
</html>
